﻿using System;
using System.Collections.Generic;
using System.Text;

using Vitruvian.Logging;
using Vitruvian.Testing;

namespace SpotItComponents
{
    /// <summary>
    /// Universe
    /// 
    /// Used to compute the cards for a Deck
    /// 
    /// See http://math.stackexchange.com/questions/36798/what-is-the-math-behind-the-game-spot-it
    /// </summary>
    [ContainsTests("Components.Universe")]    
    public class Universe
    {
        private static Logger logger = Logger.GetLogger(typeof(Universe));
        private static int[] primeNumbers = new int[] { 0, 2, 3, 5, 7, 11};
        private static List<Universe> possibleUniverses = null;

        private int primeNumber;
        private int universeSize;
        private Dictionary<string, UniverseValue> values = null;

        public Universe() { }

        public Universe(int primeNumber)
        {
            logger.DebugFormat("Create a universe for primeNumber {0}", primeNumber);

            if (primeNumber > primeNumbers[0] && primeNumber <= primeNumbers[primeNumbers.Length - 1])
            {
                this.primeNumber = primeNumber;
                this.universeSize = primeNumber * primeNumber + primeNumber + 1;
                values = new Dictionary<string, UniverseValue>(universeSize);
                int valueIndex = 0;
                for (int a = 0; a < primeNumber; a++)
                    for (int b = 0; b < primeNumber; b++)
                    {
                        UniverseValue v = new PairUniverseValue(valueIndex++, a, b);
                        values.Add(v.Key, v);
                    }
                for (int x = 0; x < primeNumber; x++)
                {
                    UniverseValue v = new SingletonUniverseValue(valueIndex++, x);
                    values.Add(v.Key, v);
                }
                UniverseValue inf = new InfinityUniversalValue(valueIndex++);
                values.Add(inf.Key, inf);
            }
        }

        public int SymbolsPerCards { get { return (primeNumber>0) ? primeNumber + 1 : 0; } }
        public int MinimumNumberOfSymbols { get { return universeSize; } }
        public int MaximumNumberOfCards { get { return universeSize; } }

        public static List<Universe> PossibleUniverses
        {
            get
            {
                if (possibleUniverses == null)
                {
                    logger.Debug("Create the list of possible universes");
                    possibleUniverses = new List<Universe>();
                    foreach (int x in primeNumbers)
                        possibleUniverses.Add(new Universe(x));
                }

                return possibleUniverses;
            }
        }

        public static Universe FindUniverseBasedOnCards(int numberOfCards)
        {
            Universe result = null;

            if (numberOfCards>0)
            {
                for (int i = 0; i < PossibleUniverses.Count - 1 && result == null; i++)
                    if (numberOfCards > PossibleUniverses[i].MaximumNumberOfCards &&
                        numberOfCards <= PossibleUniverses[i + 1].MaximumNumberOfCards)
                        result = PossibleUniverses[i+1];
            }
            return result;
        }

        public static Universe FindUniverseBasedOnSymbols(int numberOfSymbols)
        {
            Universe result = null;
            if (numberOfSymbols > 0)
            {
                if (numberOfSymbols >= PossibleUniverses[PossibleUniverses.Count - 1].MinimumNumberOfSymbols)
                    result = PossibleUniverses[PossibleUniverses.Count - 1];
                else
                    for (int i = 0; i < PossibleUniverses.Count - 1 && result == null; i++)
                        if (numberOfSymbols >= PossibleUniverses[i].MinimumNumberOfSymbols &&
                            numberOfSymbols < PossibleUniverses[i + 1].MinimumNumberOfSymbols)
                            result = PossibleUniverses[i];
            }
            return result;
        }

        public List<List<int>> CardCombinations()
        {
            logger.Debug("Compute the card combinations for a universe");

            List<List<int>> result = new List<List<int>>();
            string key;
            List<int> valueIndexList;

            // Add the super card
            logger.Debug("Start a new type-3 card");
            valueIndexList = new List<int>(SymbolsPerCards);
            for (int x = 0; x < primeNumber; x++)
            {
                key = x.ToString();
                AddValueToCard(valueIndexList, key);
            }
            AddValueToCard(valueIndexList, "Inf");

            logger.Debug("Finished with current card");
            result.Add(valueIndexList);

            // Add the second type of cards
            for (int c = 0; c < primeNumber; c++)
            {
                logger.Debug("Start a new type-2 card");
                valueIndexList = new List<int>(SymbolsPerCards);
                for (int x = 0; x < primeNumber; x++)
                {
                    key = string.Format("{0}.{1}", c, x);
                    AddValueToCard(valueIndexList, key);
                }
                AddValueToCard(valueIndexList, "Inf");

                logger.Debug("Finished with current card");
                result.Add(valueIndexList);
            }

            // Add the first type of cards
            for (int a = 0; a < primeNumber; a++)
            {
                for (int b = 0; b < primeNumber; b++)
                {
                    logger.Debug("Start a new type-1 card");
                    valueIndexList = new List<int>(SymbolsPerCards);
                    for (int x = 0; x < primeNumber; x++)
                    {
                        key = string.Format("{0}.{1}", x, (a * x + b) % primeNumber);
                        AddValueToCard(valueIndexList, key);
                    }
                    key = a.ToString();
                    AddValueToCard(valueIndexList, key);

                    logger.Debug("Finished with current card");
                    result.Add(valueIndexList);
                }
            }

            return result;
        }

        private void AddValueToCard(List<int> cardValueIndexList, string valueKey)
        {
            if (values.ContainsKey(valueKey))
            {
                UniverseValue value = values[valueKey];
                logger.DebugFormat("Add universe value {0} (index={1}) to current card", value.Key, value.Index);
                cardValueIndexList.Add(value.Index);
            }
            else
                throw new ApplicationException("Invalid Universe Value Key = " + valueKey);
        }

        private abstract class UniverseValue
        {
            private int index;

            public UniverseValue(int index)
            {
                this.index = index;
            }

            public int Index { get { return index; } }
            public abstract string Key { get; }
        }

        private class PairUniverseValue : UniverseValue
        {
            private int a;
            private int b;
            private string key;

            public PairUniverseValue(int index, int a, int b) : base(index)
            {
                this.a = a;
                this.b = b;
                this.key = string.Format("{0}.{1}", a, b);
            }

            public int A { get { return a; } }
            public int B { get { return b; } }
            public override string Key { get { return key; } }
        }

        private class SingletonUniverseValue : UniverseValue
        {
            private int a;
            private string key;

            public SingletonUniverseValue(int index, int a) : base(index)
            {
                this.a = a;
                this.key = a.ToString();
            }

            public int A { get { return a; } }
            public override string Key { get { return key; } }
        }

        private class InfinityUniversalValue : UniverseValue
        {
            public InfinityUniversalValue(int index) : base(index) { }
            public override string Key { get { return "Inf"; } }
        }

        #region Unit Test Cases
#if (DEBUG)
        [Test(1)]
        private void Test_Constructor_and_Properties()
        {
            Universe u = new Universe(0);
            Assert.Equals(0, u.MaximumNumberOfCards);
            Assert.Equals(0, u.MinimumNumberOfSymbols);
            Assert.Equals(0, u.SymbolsPerCards);
            Assert.IsNull(u.values);

            u = new Universe(2);
            Assert.Equals(7, u.MaximumNumberOfCards);
            Assert.Equals(7, u.MinimumNumberOfSymbols);
            Assert.Equals(3, u.SymbolsPerCards);
            Assert.IsNotNull(u.values);
            Assert.Equals(7,u.values.Count);

            u = new Universe(3);
            Assert.Equals(13, u.MaximumNumberOfCards);
            Assert.Equals(13, u.MinimumNumberOfSymbols);
            Assert.Equals(4, u.SymbolsPerCards);
            Assert.Equals(13, u.values.Count);

            u = new Universe(7);
            Assert.Equals(57, u.MaximumNumberOfCards);
            Assert.Equals(57, u.MinimumNumberOfSymbols);
            Assert.Equals(8, u.SymbolsPerCards);
            Assert.Equals(57, u.values.Count);

            u = new Universe(11);
            Assert.Equals(133, u.MaximumNumberOfCards);
            Assert.Equals(133, u.MinimumNumberOfSymbols);
            Assert.Equals(12, u.SymbolsPerCards);
            Assert.Equals(133, u.values.Count);

            u = new Universe(-1);
            Assert.Equals(0, u.MaximumNumberOfCards);
            Assert.Equals(0, u.MinimumNumberOfSymbols);
            Assert.Equals(0, u.SymbolsPerCards);
            Assert.IsNull(u.values);

            u = new Universe(12);
            Assert.Equals(0, u.MaximumNumberOfCards);
            Assert.Equals(0, u.MinimumNumberOfSymbols);
            Assert.Equals(0, u.SymbolsPerCards);
            Assert.IsNull(u.values);

        }

        [Test(2)]
        private void Test_FindUniverse()
        {
            Assert.IsTrue(PossibleUniverses.Count > 0);

            Universe u = FindUniverseBasedOnCards(6);
            Assert.Equals(7, u.MaximumNumberOfCards);
            Assert.Equals(7, u.MinimumNumberOfSymbols);

            u = FindUniverseBasedOnCards(56);
            Assert.Equals(57, u.MaximumNumberOfCards);
            Assert.Equals(57, u.MinimumNumberOfSymbols);
            Assert.Equals(8, u.SymbolsPerCards);

            u = FindUniverseBasedOnCards(57);
            Assert.Equals(57, u.MaximumNumberOfCards);
            Assert.Equals(57, u.MinimumNumberOfSymbols);
            Assert.Equals(8, u.SymbolsPerCards);

            u = FindUniverseBasedOnCards(58);
            Assert.Equals(73, u.MaximumNumberOfCards);
            Assert.Equals(73, u.MinimumNumberOfSymbols);
            Assert.Equals(9, u.SymbolsPerCards);

            u = FindUniverseBasedOnCards(0);
            Assert.IsNull(u);

            u = FindUniverseBasedOnCards(-1);
            Assert.IsNull(u);

        }

        [Test(3)]
        private void Test_Combinations()
        {
            Assert.IsTrue(PossibleUniverses.Count > 0);

            Universe u = FindUniverseBasedOnCards(57);
            Assert.Equals(57, u.MaximumNumberOfCards);
            Assert.Equals(57, u.MinimumNumberOfSymbols);

            List<List<int>> cardList = u.CardCombinations();
            Assert.IsNotNull(cardList);
            Assert.Equals(7, cardList.Count);
            foreach (List<int> card in cardList)
            {
                Assert.Equals(8, card.Count);
                foreach (List<int> otherCard in cardList)
                {
                    if (card != otherCard)
                    {
                        int matchCount = MatchCards(card, otherCard);
                        Assert.Equals(1, matchCount);
                    }
                }
                string tmp = "";
                foreach (int sIndex in card)
                    tmp += sIndex.ToString() + " ";
                logger.Debug(tmp);
            }
        }

        private int MatchCards(List<int> card1, List<int> card2)
        {
            int totalMatches = 0;
            foreach (int s1 in card1)
                foreach (int s2 in card2)
                    if (s1 == s2)
                        totalMatches++;

            return totalMatches;
        }
#endif
        #endregion


    }
}
